use spring_axum::{
    controller, route_get, route_post, route_put, Component, Path,
    Json, JsonRejection, AppError, AppResult,
};
use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::services::greeting_service::GreetingService;
use crate::services::user_service::UserService;
#[cfg(feature = "sqlx_postgres")]
use spring_axum::Json as AxumJson;
#[cfg(feature = "sqlx_postgres")]
use serde_json::json;
#[cfg(feature = "sqlx_postgres")]
use spring_axum::AppResult as AxumAppResult;
#[cfg(feature = "sqlx_postgres")]
use spring_axum::StatusCode as AxumStatusCode;
#[cfg(feature = "sqlx_postgres")]
use spring_axum::sqlx_pool;
#[cfg(feature = "sqlx_postgres")]
use spring_axum::sqlx_transaction;
#[cfg(feature = "sqlx_postgres")]
use sqlx::query as sqlx_query;

#[route_get("/hello")]
pub async fn hello() -> &'static str {
    "Hello, Spring Axum"
}

#[route_get("/greet/{name}")]
pub async fn greet(Component(svc): Component<GreetingService>, Path(name): Path<String>) -> String {
    svc.greet(&name)
}

#[route_post("/echo")]
pub async fn echo(payload: Result<Json<serde_json::Value>, JsonRejection>) -> AppResult<String> {
    match payload {
        Ok(Json(v)) => Ok(v["msg"].as_str().unwrap_or("").to_string()),
        Err(err) => Err(AppError::from(err)),
    }
}

#[derive(Deserialize, Serialize, Validate)]
pub struct User {
    #[validate(length(min = 1))]
    pub name: String,
}

#[spring_axum_macros::validate_json]
#[route_put("/users/{id}")]
pub async fn update_user(
    Path(id): Path<String>,
    spring_axum::ValidatedJson(user): spring_axum::ValidatedJson<User>,
) -> AppResult<String> {
    Ok(format!("{}:{}", id, user.name))
}

#[derive(Deserialize, Validate)]
pub struct SearchParams {
    #[validate(length(min = 3))]
    pub q: String,
}

#[spring_axum_macros::validate_query]
#[route_get("/search")]
pub async fn search(
    spring_axum::ValidatedQuery(params): spring_axum::ValidatedQuery<SearchParams>,
) -> AppResult<String> {
    Ok(params.q)
}

// Typed headers + JSON serialization example, with route-level middleware
use axum_extra::TypedHeader;
use axum_extra::headers::ContentType;

#[route_get("/headers", layer = spring_axum::InterceptorLayer::new(crate::interceptors::log_interceptor::LogInterceptor))]
pub async fn headers() -> (TypedHeader<ContentType>, Json<serde_json::Value>) {
    let ct = ContentType::json();
    (
        TypedHeader(ct),
        Json(serde_json::json!({"ok": true, "message": "typed headers"})),
    )
}

#[derive(Deserialize, Validate)]
pub struct ProfileForm {
    #[validate(length(min = 1))]
    pub username: String,
    #[validate(email)]
    pub email: String,
}

#[spring_axum_macros::validate_form]
#[route_post("/profile")]
pub async fn submit_profile(
    Component(svc): Component<UserService>,
    spring_axum::ValidatedForm(form): spring_axum::ValidatedForm<ProfileForm>,
) -> AppResult<String> {
    // Delegate logic composition to service; mapper only exposes SQL calls
    let mapper = crate::mappers::user_mapper::UserMapper::default();
    svc.save_profile_and_queries(&mapper, &form.username, &form.email).await?;
    Ok(format!("{}|{}", form.username, form.email))
}

// 事务切面示例：命中 pointcut 时，该路由的整个请求在事务中执行
#[spring_axum_macros::validate_form]
#[route_post("/tx/profile")]
pub async fn submit_profile_tx(
    Component(svc): Component<UserService>,
    spring_axum::ValidatedForm(form): spring_axum::ValidatedForm<ProfileForm>,
) -> AppResult<String> {
    let mapper = crate::mappers::user_mapper::UserMapper::default();
    // 当 email 包含 "rollback" 时，service 返回 BadRequest，事务将回滚；否则提交
    svc.save_profile_tx(&mapper, &form.username, &form.email).await?;
    Ok(format!("{}|{}", form.username, form.email))
}

#[controller(hello, greet, echo, update_user, headers, search, submit_profile, submit_profile_tx)]
#[allow(dead_code)]
pub struct Api;

// Optional DB status endpoint, only compiled when sqlx_postgres feature is enabled
#[cfg(feature = "sqlx_postgres")]
#[route_get("/db/status")]
pub async fn db_status() -> AxumAppResult<AxumJson<serde_json::Value>> {
    let set = sqlx_pool().is_some();
    Ok(AxumJson(json!({ "pool_set": set })))
}

// Simple transaction test: run `SELECT 1` within a transaction
#[cfg(feature = "sqlx_postgres")]
#[route_get("/db/tx-test")]
pub async fn db_tx_test() -> AxumAppResult<AxumJson<serde_json::Value>> {
    let executed = sqlx_transaction(|conn| async move {
        // Try a trivial read to exercise BEGIN/COMMIT
        sqlx_query("SELECT 1").fetch_one(conn).await
            .map(|_| true)
            .map_err(|e| AppError::Internal(format!("sqlx error: {}", e)))
    }).await.is_ok();
    Ok(AxumJson(json!({ "tx_ok": executed })))
}